﻿//Copyright (C) <2013>  <jonathan cleeve norton> All Rights Reserved 
//Contact jon.norton@fin-plus.co.uk website <http://www.fin-plus.co.uk/>
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Threading;

using FinPlusCommon;
using log = Logger.Logger;
using u = FinPlusUtility.Utility;

namespace FinPlusComponents
{
    public class QLEngineYieldCurves : FinPlusComponent
    {
        public override string Id { get; set; }
        public override string[] States { get { return new string[] { }; } }
        public override string[] TestStates { get { return new string[] { "IsValid" }; } }
        public override IFinPlusFunctions Model { get { return null; } }
        public override string ModelTest { get { return null; } }
        public override bool IsValid { get; set; }

        private enum Params { CurveName, LinkedCurve };
        private enum DockControls { FinPlusDataConn, FinPlusChildService, QLAnalytics, Default }
        private enum Connection { Instruments, YieldCurveBuilds, InstrumentBuilds, Exception }
        private enum BuildEvents { Update, Refresh, UpdateAndRefresh }
        
        private IFinPlusComponent _instrumentConn, _yieldCurveBuildConn, _instrumentBuildConn;
        private QLAnalytics _analytics;
        private readonly YieldCurves _yieldCurves;
        private readonly BuildEvents _buildEvent;
        private bool _hasInitialised;

        //construct
        public QLEngineYieldCurves(string buildEvents)
        {
            _buildEvent = u.EnumTryParse<BuildEvents>(buildEvents, BuildEvents.UpdateAndRefresh);
            _yieldCurves = new YieldCurves();
        }

        //common control interface
        public override void Dock(object component, string dock) 
        {
            try
            {
                switch (u.EnumTryParse<DockControls>(component.GetType().Name, DockControls.Default))
                {
                    case DockControls.QLAnalytics: AddQLAnalytics((QLAnalytics)component, dock); break;
                    case DockControls.FinPlusChildService:
                    case DockControls.FinPlusDataConn:
                    case DockControls.Default: AddConnection((IFinPlusComponent)component, dock); break;
                }
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        public override void Initialise()
        {
            try
            {
                if (_hasInitialised) return;

                AddCurves();

                IsValid = _hasInitialised = true;
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        public override void Dispose()
        {
            if(_instrumentConn != null)
                _instrumentConn.Dispose(); 

            if(_yieldCurveBuildConn != null)
                _yieldCurveBuildConn.Dispose();

            if (_instrumentBuildConn != null)
                _instrumentBuildConn.Dispose(); 
        }

        public override void CommandRouter(IFinPlusFunction func) { }//levels via data conn trap/inject through conn observability

        //private
        private void AddCurves()
        {
            foreach (var yieldCurveBuild in _yieldCurveBuildConn.Model.Values)
            {
                var curveName = yieldCurveBuild[Params.CurveName.ToString()].ToString();
                _yieldCurves.TryAdd(curveName, new YieldCurve(_analytics, curveName, yieldCurveBuild, _instrumentBuildConn.Model, _instrumentConn.Model.ToList(Params.LinkedCurve.ToString(), curveName, ','), _buildEvent));
            }
        }

        private void AddQLAnalytics(QLAnalytics analytics, string dock)
        {
            try
            {
                _analytics = analytics;
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void AddConnection(IFinPlusComponent conn, string dock)
        {
            try
            {
                switch (u.EnumTryParse<Connection>(dock, Connection.Exception))
                {
                    case Connection.YieldCurveBuilds: _yieldCurveBuildConn = conn; break;
                    case Connection.Instruments: _instrumentConn = conn; break;
                    case Connection.InstrumentBuilds: _instrumentBuildConn = conn; break;
                    case Connection.Exception: throw new NotImplementedException();
                }
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        //all helper class of component FinPlusEngineYieldCurves 
        private class YieldCurves : ConcurrentDictionary<string, YieldCurve> { }

        private class YieldCurve : ConcurrentDictionary<string, IRate>
        {
            private enum Params { LevelUpdate, Refresh, Shock, CurveName, Instrument, FunctionName, NumDepo, NumFut, NumFra, NumSwap, Index, DayCount, Tolerance, Holidays };
            private enum BuildType { DepositLoanBuild, FowardRateAgreementBuild, IRFutureBuild, VanillaSwapBuild, Unknown };

            private IList<IFinPlusFunction> _instruments;
            private QLAnalytics _analytics;
            private IFinPlusFunctions _instrumentBuilds;
            private IFinPlusFunction _yieldCurveBuild;
            private BuildEvents _buildEvents;
            private int _numDepo, _numFut, _numFra, _numSwap, _depoCount, _fraCount, _futCount, _swapCount;
            private string _curveName, _index, _dayCount, _holidays;
            private double _tolerance;
            bool _hasBuilt;
  
            //construct
            public YieldCurve(QLAnalytics analytics, string curveName, IFinPlusFunction yieldCurveBuild, IFinPlusFunctions instrumentBuilds, IList<IFinPlusFunction> instruments, BuildEvents buildEvents)
            {
                _analytics = analytics;
                _curveName = curveName;
                _yieldCurveBuild = yieldCurveBuild;
                _instrumentBuilds = instrumentBuilds;
                _instruments = instruments;
                _index = _yieldCurveBuild[Params.Index.ToString()].ToString();
                _dayCount = _yieldCurveBuild[Params.DayCount.ToString()].ToString();
                _tolerance = double.Parse(_yieldCurveBuild[Params.Tolerance.ToString()].ToString());
                _holidays = _yieldCurveBuild[Params.Holidays.ToString()].ToString();
                _numDepo = (int)_yieldCurveBuild.Value(Params.NumDepo.ToString());
                _numFut = (int)_yieldCurveBuild.Value(Params.NumFut.ToString());
                _numFra = (int)_yieldCurveBuild.Value(Params.NumFra.ToString());
                _numSwap = (int)_yieldCurveBuild.Value(Params.NumSwap.ToString());
                _buildEvents = buildEvents;
                Initialise();
            }

            public void Initialise()
            {
                foreach (var instrument in _instruments)
                {
                    if (_buildEvents == BuildEvents.Refresh || _buildEvents == BuildEvents.UpdateAndRefresh)
                        instrument.Get(Params.Refresh.ToString(), string.Empty, Curve_Refresh);

                    instrument.Get(Params.LevelUpdate.ToString(), new Function(), Curve_Update);
                    instrument.Get(Params.Shock.ToString(), new Function(), Curve_Shock);

                    var instrumentName = instrument[Params.Instrument.ToString()].ToString();
                    var instrumentBuild = _instrumentBuilds.First("Name='Instrument' And Value='" + instrumentName + "'");

                    if (instrumentBuild != null)
                    {
                        this[instrumentName] = AddRate(instrumentName, instrument, instrumentBuild);
                        instrument.Get(Params.Refresh.ToString(), string.Empty).Set(instrumentName);
                    }
                }
            }

            //private
            private IRate AddRate(string instrumentName, IFinPlusFunction instrument, IFinPlusFunction instrumentBuild)
            {
                switch(u.EnumTryParse<BuildType>( instrumentBuild[Params.FunctionName.ToString()].ToString(), BuildType.Unknown))
                {
                    case BuildType.DepositLoanBuild: _depoCount++; return new DepoRate(_analytics, instrumentName, instrument, instrumentBuild);
                    case BuildType.FowardRateAgreementBuild: _fraCount++; return new FraRate(_analytics, instrumentName, instrument, instrumentBuild);
                    case BuildType.IRFutureBuild: _futCount++; return new FutRate(_analytics, instrumentName, instrument, instrumentBuild);
                    case BuildType.VanillaSwapBuild: _swapCount++; return new SwapRate(_analytics, instrumentName, instrument, instrumentBuild);
                    case BuildType.Unknown: break; //TODO 
                }

                return null;
            }

            private void BuildCurve()
            {
                try
                {
                    string res;
                    lock (_analytics.QL)
                    {
                        res = _analytics.QL.YieldCurve(_analytics.MarketName, _curveName, _analytics.DiscountCurve, _analytics.AsOf, Rates(_analytics.BuySell), _dayCount, _tolerance, _holidays);
                        _hasBuilt = true;
                    }
                }
                catch (Exception e)
                {
                    log.Log(Logger.Level.Error, _yieldCurveBuild.ToString(), e);
                }
            }

            private string Rates(string buySell)
            {
                return string.Join(",", this.Values.Select(f => f.Build(buySell)).ToArray<string>());
            }

            private bool IsValid()
            {
                if (_numDepo != _depoCount || _numFut != _futCount || _numFra != _fraCount || _numSwap != _swapCount)
                    return false;

                foreach (var rate in this.Values)
                    if (Double.IsNaN(rate.Value))
                        return false;

                return true;
            }

            private void Curve_Update(object sender, PropertyChangedEventArgs a)
            {
                var level = (IFinPlusFunction)((IFinPlusItem)sender).Object;
                var instrument = level[Params.Instrument.ToString()].ToString();
                
                if (this.ContainsKey(instrument))
                {
                    IRate rate;
                    if (this.TryGetValue(instrument, out rate))
                    {
                        if (rate.Update(level) && IsValid() && (_buildEvents == BuildEvents.Update || _buildEvents == BuildEvents.UpdateAndRefresh || !_hasBuilt))
                            BuildCurve();
                    }
                }
            }

            private void Curve_Refresh(object sender, PropertyChangedEventArgs a)
            {
                if ((((IFinPlusItem)sender).Value.Equals(_curveName)) && IsValid())
                    BuildCurve();
            }

            private void Curve_Shock(object sender, PropertyChangedEventArgs a)
            {
                var shock = (IFinPlusFunction)((IFinPlusItem)sender).Object;
                var curveName = shock[Params.CurveName.ToString()].ToString();
                if(curveName.Equals(_curveName))
                {
                    var instrumentName = shock[Params.Instrument.ToString()].ToString();
                    var value = (int)shock.Get(Params.Shock.ToString()).Object;
                    this[instrumentName].Shock = value;
                    if (value != 0 && IsValid()) BuildCurve();
                }
            }
        }

        private interface IRate 
        {
            double Value { get; }
            int Shock { get; set; }
            string Instrument { get; }

            bool Update(IFinPlusFunction level);
            string Build(string buySell);
        }

        private class DepoRate : IRate
        {
            public double Value { get { return _level.Value; } }
            public int Shock { get; set; }
            public string Instrument { get { return _instrumentName; } }

            private enum Params {ShockUnit, Maturity, SettlementDays, DayCount, BizConv, Holidays };
            private FinPlusLevel _level;
            private QLAnalytics _analytics;
            private IFinPlusFunction _instrumentBuild, _instrument;
            private string _instrumentName, _tenor, _dayCount, _bizConv, _holidays;
            private double _shockUnit;
            private int _fixingDays;

            public DepoRate(QLAnalytics analytics, string instrumentName, IFinPlusFunction instrument, IFinPlusFunction instrumentBuild)
            {
                _analytics = analytics;
                _instrumentName = instrumentName;
                _instrument =  instrument;
                _instrumentBuild = instrumentBuild;
                
                _level = new FinPlusLevel(analytics.BuySell);
                _tenor = _instrumentBuild[Params.Maturity.ToString()].ToString();
                _fixingDays = (int)_instrumentBuild.Value(Params.SettlementDays.ToString());
                _dayCount = _instrumentBuild[Params.DayCount.ToString()].ToString();
                _bizConv = _instrumentBuild[Params.BizConv.ToString()].ToString();
                _holidays = _instrumentBuild[Params.Holidays.ToString()].ToString();
                _shockUnit = (double)_instrument.Get(Params.ShockUnit.ToString()).Object;
            }

            public string Build(string buySell)
            {
                lock (_analytics.QL)
                    return _analytics.QL.DepoRate(_analytics.MarketName, _instrumentName, _level.Value + _shockUnit * Shock, _tenor, _fixingDays, _dayCount, _bizConv, _holidays);
            }

            public bool Update(IFinPlusFunction level)
            {
                return _level.LevelUpdate(level);
            }
        }

        private class FraRate : IRate
        {
            public double Value { get { return _level.Value; } }
            public int Shock { get; set; }
            public string Instrument { get { return _instrumentName; } }

            private enum Params {ShockUnit, Start, Maturity, SettlementDays, DayCount, BizConv, Holidays };
            private FinPlusLevel _level;
            private QLAnalytics _analytics;
            private IFinPlusFunction _instrumentBuild, _instrument;
            private string _instrumentName, _dayCount, _bizConv, _holidays;
            private int _settlementDays, _start, _end;
            private double _shockUnit;

            public FraRate(QLAnalytics analytics, string instrumentName, IFinPlusFunction instrument, IFinPlusFunction instrumentBuild)
            {
                _analytics = analytics;
                _instrumentName = instrumentName;
                _instrument = instrument;
                _instrumentBuild = instrumentBuild;
                
                _level = new FinPlusLevel(analytics.BuySell);
                _dayCount = _instrumentBuild[Params.DayCount.ToString()].ToString();
                _bizConv = _instrumentBuild[Params.BizConv.ToString()].ToString();
                _holidays = _instrumentBuild[Params.Holidays.ToString()].ToString();
                lock (_analytics.QL)
                {
                    _start = _analytics.QL.NextBizDate(_analytics.AsOf, _instrumentBuild[Params.Start.ToString()].ToString(), _holidays, false, _holidays);
                    _end = _analytics.QL.NextBizDate(_analytics.AsOf, _instrumentBuild[Params.Maturity.ToString()].ToString(), _holidays, false, _holidays);
                }
                _settlementDays = (int)_instrumentBuild.Value(Params.SettlementDays.ToString());
                _shockUnit = (double)_instrument.Get(Params.ShockUnit.ToString()).Object;
            }

            public string Build(string buySell)
            {
                lock (_analytics.QL)
                    return _analytics.QL.FraRate(_analytics.MarketName, _instrumentName, _level.Value + _shockUnit * Shock, _start, _end, _settlementDays, _dayCount, _bizConv, _holidays);
            }

            public bool Update(IFinPlusFunction level)
            {
                return _level.LevelUpdate(level);
            }
        }

        private class FutRate : IRate
        {
            public double Value { get { return _level.Value; } }
            public int Shock { get; set; }
            public string Instrument { get { return _instrumentName; } }

            private enum Params { ShockUnit, StartDate, FutMonths, DayCount, BizConv, Holidays };
            private FinPlusLevel _level;
            private QLAnalytics _analytics;
            private IFinPlusFunction _instrumentBuild, _instrument;
            private string _instrumentName, _dayCount, _bizConv, _holidays;
            private int _startDate, _futMonths;
            private double _shockUnit;

            public FutRate(QLAnalytics analytics, string instrumentName, IFinPlusFunction instrument, IFinPlusFunction instrumentBuild)
            {
                _analytics = analytics;
                _instrumentName = instrumentName;
                _instrument = instrument; 
                _instrumentBuild = instrumentBuild;

                _level = new FinPlusLevel(analytics.BuySell);
                _startDate = (int)_instrumentBuild.Value(Params.StartDate.ToString());
                _futMonths = (int)_instrumentBuild.Value(Params.FutMonths.ToString());
                _dayCount = _instrumentBuild[Params.DayCount.ToString()].ToString();
                _bizConv = _instrumentBuild[Params.BizConv.ToString()].ToString();
                _holidays = _instrumentBuild[Params.Holidays.ToString()].ToString();
                _shockUnit = (double)_instrument.Get(Params.ShockUnit.ToString()).Object;
            }

            public string Build(string buySell)
            {
                lock (_analytics.QL)
                     return _analytics.QL.FutRate(_analytics.MarketName, _instrumentName, _level.Value + _shockUnit * Shock, _startDate, _futMonths, _dayCount, _bizConv, _holidays);
            }

            public bool Update(IFinPlusFunction level)
            {
               return _level.LevelUpdate(level);
            }
        }

        private class SwapRate : IRate
        {
            public double Value { get { return _level.Value; } }
            public int Shock { get; set; }
            public string Instrument { get { return _instrumentName; } }

            private enum Params {ShockUnit, Maturity, SettlementDays, Index, FixLegFrq, FixLegDayCount, FixLegConv, Holidays };
            private FinPlusLevel _level;
            private QLAnalytics _analytics;
            private IFinPlusFunction _instrumentBuild, _instrument;
            private string _instrumentName, _tenor, _floatLegIndex, _fixedLegFreq, _fixedLegDayCount, _fixedLegBizConv, _holidays;
            private int _settlementDays;
            private double _shockUnit;

            public SwapRate(QLAnalytics analytics, string instrumentName, IFinPlusFunction instrument, IFinPlusFunction instrumentBuild)
            {
                _analytics = analytics;
                _instrumentName = instrumentName;
                _instrument = instrument;
                _instrumentBuild = instrumentBuild;
                
                _level = new FinPlusLevel(analytics.BuySell);
                _tenor = _instrumentBuild[Params.Maturity.ToString()].ToString();
                _settlementDays = (int)_instrumentBuild.Value(Params.SettlementDays.ToString());
                _floatLegIndex = _instrumentBuild[Params.Index.ToString()].ToString();
                _fixedLegFreq = _instrumentBuild[Params.FixLegFrq.ToString()].ToString();
                _fixedLegDayCount = _instrumentBuild[Params.FixLegDayCount.ToString()].ToString();
                _fixedLegBizConv = _instrumentBuild[Params.FixLegConv.ToString()].ToString();
                _holidays = _instrumentBuild[Params.Holidays.ToString()].ToString();
                _shockUnit = (double)_instrument.Get(Params.ShockUnit.ToString()).Object;
            }

            public string Build(string buySell)
            {
                lock (_analytics.QL)
                     return _analytics.QL.SwapRate(_analytics.MarketName, _instrumentName, _level.Value + _shockUnit * Shock, _tenor, _settlementDays, 0, _floatLegIndex, _fixedLegFreq, _fixedLegDayCount, _fixedLegBizConv, _holidays);
            }

            public bool Update(IFinPlusFunction level)
            {
                return _level.LevelUpdate(level);
            }
        }
    }
}
